package com.example.generictest.utils;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.text.CharSequenceUtil;
import com.example.generictest.bean.Student;
import com.example.generictest.exception.ConfigException;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.yaml.snakeyaml.Yaml;

import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author lee
 * @date 2022年03月16日 11:10
 */
public class YamlParser {

    private YamlParser() {
        throw new IllegalStateException("Utility class");
    }

    /**
     * 解析配置
     *
     * @param type 配置类
     * @return T 配置类类型
     * @throws ConfigException 如果解析配置文件失败
     */
    public static <T> T parse(Class<T> type) throws ConfigException {
        SourceData source = getSource(type);
        Map<String, Object> configMap = loadData(source);
        T config = createConfigInstance(type);
        for (Field field : FieldUtils.getAllFieldsList(type)) {
            fillField(config, field, configMap, type, source.getName());
        }
        return config;
    }

    /**
     * 解析Yaml配置为Map对象
     *
     * @param source 数据源
     * @return Map对象
     * @throws ConfigException 如果解析失败
     */
    private static Map<String, Object> loadData(SourceData source) throws ConfigException {
        try {
            return new Yaml().load(source.getStream());
        } catch (Exception e) {
            throw new ConfigException("解析配置文件失败: {}", source.getName());
        }
    }

    /**
     * 填充配置对象的字段
     *
     * @param config     配置对象
     * @param field      要填充的字段
     * @param configMap  数据Map
     * @param type       配置类
     * @param sourceName 数据源名称
     * @throws ConfigException 如果解析配置出错
     */
    private static <T> void fillField(T config, Field field, Map<String, Object> configMap,
                                      Class<T> type, String sourceName) throws ConfigException {
        Value annotation = field.getAnnotation(Value.class);
        if (annotation == null) return;

        String key = annotation.value();
        if (key.isEmpty()) {
            throw new ConfigException("{}.{}: 配置名不能为空", type.getSimpleName(), field.getName());
        }

        Object value = BeanUtil.getProperty(configMap, key);

        //判断内容是否为字符串.增强占位符。
        if (value instanceof String) {
            value = generatePlaceholderContent(value.toString(), configMap);
        }

        if (value == null && !annotation.defaultValue().isEmpty()) {
            value = annotation.defaultValue();
        }
        if (value == null) {
            if (field.getType().isPrimitive() || BeanUtil.getProperty(config, field.getName()) == null) {
                throw new ConfigException("{} - {}是必填项", sourceName, key);
            }
        } else {
            BeanUtil.setProperty(config, field.getName(), value);
        }
    }

    /**
     * 根据配置类获取配置文件输入流
     *
     * @param type 配置类
     * @return 配置文件输入流
     * @throws ConfigException 如果找不到配置文件
     */
    private static <T> SourceData getSource(Class<T> type) throws ConfigException {
        String filename = getFilename(type);
        InputStream in = YamlParser.class.getClassLoader().getResourceAsStream(filename);
        if (in == null) {
            throw new ConfigException("找不到配置文件: {}", filename);
        }
        return new SourceData(filename, in);
    }

    /**
     * 根据配置类上的注解获取对应的配置文件名
     *
     * @param type 配置类
     * @return 配置文件名
     * @throws ConfigException 如果获取注解失败
     */
    private static <T> String getFilename(Class<T> type) throws ConfigException {
        ConfigSource source = type.getAnnotation(ConfigSource.class);
        if (source == null) {
            throw new ConfigException("{}: 缺少数据源", type.getSimpleName());
        }
        return source.value();
    }

    /**
     * 实例化配置类
     *
     * @param type 配置类
     * @return T 配置类类型
     * @throws ConfigException 如果实例化失败
     */
    private static <T> T createConfigInstance(Class<T> type) throws ConfigException {
        try {
            return type.getConstructor().newInstance();
        } catch (Exception e) {
            throw new ConfigException(CharSequenceUtil.format("{}: 实例化配置类失败", type.getName()), e);
        }
    }

    /***
     * 组装占位符内容
     *
     * @param o 需要替换的占位符内容
     * @param load yaml文件内容
     * @return: void
     **/
    private static String generatePlaceholderContent(String o, Map<String, Object> load) {
        StringBuffer result = new StringBuffer();
        String pattern = "\\$\\{(.+?)\\}";
        Pattern p = Pattern.compile(pattern);
        Matcher matcher = p.matcher(o);
        while (matcher.find()) {
            String key = matcher.group(1);
            Object valObj = BeanUtil.getProperty(load, key);
            matcher.appendReplacement(result, valObj.toString());
        }
        matcher.appendTail(result);
        return result.toString();
    }
}